#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/minmax.h>
#include <linux/poll.h>

#define LOGTAG "chrdev "

#define DEVICE_NUMBER 1                                             // 申请设备号的数量
#define DEVICE_SNAME "schrdev"                                      // 静态注册 名称
#define DEVICE_ANAME "achrdev"                                      // 动态注册 名称
#define DEVICE_MINOR_NUMBER 0                                       // 次设备号起始地址，通常是0

static int major_num = 0, minor_num = 0;
static int dev_param = 1234;
static int dev_args[5] = {11, 22, 33, 44, 55};
static int dev_argc = 5;

/* module_param(name, type, perm);
 * name：要传递进去的参数的名称
 * type：类型
 * perm：参数读写的权限 S_IRUSR 400
 */
module_param(dev_param, int, S_IRUSR);
module_param(major_num, int, S_IRUSR);
module_param(minor_num, int, S_IRUSR);

/* module_param_array(name, type, &nump, perm);
 * name:要传递进去的参数的名称
 * type：类型
 * nump：实际传入的个数
 * perm：参数读写的权限
 */
module_param_array(dev_args, int, &dev_argc, S_IRUSR);

int chrdev_open(struct inode* inode, struct file* file) {
    printk("chrdev_open !!!\n");
    return 0;
}

int chrdev_release(struct inode* inode, struct file* file) {
    printk("chrdev_release !!!\n");
    return 0;
}

loff_t chrdev_llseek(struct file *file, loff_t loff, int whence) {
    printk("chrdev_llseek: loff = 0x%llX, whence = %d\n", loff, whence);
    return loff;
}

ssize_t chrdev_read(struct file* file, char __user* user, size_t size, loff_t* loff) {
    int ret;
    const char kbuf[64] = "this is from mychrdev kernal driver.";
    printk("chrdev_read: size = %lu, loff = %p, *loff = %lld\n", size, loff, loff ? *loff : -1);

    ret = copy_to_user(user, kbuf, sizeof(kbuf));
    if(ret) {
        pr_err("copy_to_user error: %d\n", ret);
        return -EIO;
    }
    return 0;
}

ssize_t chrdev_write(struct file* file, const char __user* user, size_t size, loff_t* loff) {
    int dlen, ret;
    char kbuf[64];
    pr_info("chrdev_write: size = %lu, loff = %p, *loff = %lld\n", size, loff, loff ? *loff : -1);

    dlen = min(size, sizeof(kbuf)-1);
    ret = copy_from_user(kbuf, user, dlen);
    if(ret) {
        pr_err("copy_from_user error: %d\n", ret);
        return -EIO;
    }
    kbuf[dlen] = '\0';
    pr_info("%s\n", kbuf);
    return dlen;
}

long chrdev_unlocked_ioctl(struct file* file, unsigned int request, unsigned long option) {
    printk("chrdev_unlocked_ioctl: request = %d, option = %ld\n", request, option);
    return 5678;
}

static DECLARE_WAIT_QUEUE_HEAD(my_waitq);  // 休眠要挂的等待队列

__poll_t chrdev_poll(struct file *file, struct poll_table_struct *wait) {
    unsigned int mask = 0;
    printk("chrdev_poll\n");
    poll_wait(file, &my_waitq, wait); // 不会立即休眠
    // 如果有数据，就返回 POLLIN | POLLRDNORM
    if(1) mask |= POLLIN | POLLRDNORM;
    return mask;
}

struct file_operations chrdev_fops = {
    .owner = THIS_MODULE,
    .open = chrdev_open,
    .release = chrdev_release,
    .llseek = chrdev_llseek,
    .read = chrdev_read,
    .write = chrdev_write,
    .unlocked_ioctl = chrdev_unlocked_ioctl,
    .poll = chrdev_poll,
};

struct miscdevice chrdev_dev = {
    .minor = MISC_DYNAMIC_MINOR, 
    .name = "mychrdev", 
    .fops = &chrdev_fops 
};

static int chrdev_init(void) {
    int ret, i;
    dev_t dev_num; // 保存设备号

    if(major_num) {
        // 如果major_num被手动设置，就使用静态注册设备号        
        printk("(major, minor) = (%d, %d)\n", major_num, minor_num);
        dev_num = MKDEV(major_num, minor_num);

        ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME);
        if(ret < 0) { // ret<0 注册失败
            pr_err("register_chrdev_region ERROR!!!\n");
            return -1;
        } 
        printk("register_chrdev_region: %s\n", DEVICE_SNAME);
    } else {
        // major_num没有传入，所以动态申请设备号                                                                  
        ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME);
        if(ret < 0){
            pr_err("alloc_chrdev_region ERROR!!!\n");
            return -1;
        }
        printk("alloc_chrdev_region: %s\n", DEVICE_ANAME);

        major_num = MAJOR(dev_num); // 取出来主设备号
        minor_num = MINOR(dev_num); // 取出来次设备号
        printk("(major, minor) = (%d, %d)\n", major_num, minor_num);
    }

    pr_info(LOGTAG "dev_param = %d\n", dev_param);
    for(i=0; i<dev_argc; i++) {
        pr_info(LOGTAG "dev_args[%d] = %d\n", i, dev_args[i]);
    }

    ret = misc_register(&chrdev_dev);
    if(ret < 0) {
        printk("chrdev_register failed !!!\n");
        return -1;
    }

    printk("chrdev_register succeed !!!\n");  // 在内核中无法使用c语言库，所以不用printf
    return 0;
}

static void chrdev_exit(void) {
    misc_deregister(&chrdev_dev);
    printk("chrdev exit !!!\n");
}

module_init(chrdev_init);
module_exit(chrdev_exit);

MODULE_LICENSE("GPL");  // 声明模块拥有开源许可
